﻿using UnityEngine;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.IO;
using System;
using System.Text;

namespace Network
{
    public class SocketConnection: IConnection
    {
        enum ConnectionState
        {
            STOPPED,
            CONNECTING,
            CONNECTED,
        };

        struct SendContext
        {
            public byte[] Data;
            public int StartIndex;
        }

        /// <summary>
        /// 连接状态
        /// </summary>
        ConnectionState m_state;

        /// <summary>
        /// 当前连接时间
        /// </summary>
        float m_connectingTime = 0;

        /// <summary>
        /// 超时时间
        /// </summary>
        const float ConnectTimeout = 5f;

        Queue<SendContext> m_sendQueue;

        /// <summary>
        /// 是否转换大端小端
        /// </summary>
        public bool reverseByte = true;

        static readonly public int msgTypeSize = 1;
        static readonly public int msgLengthSize = 4;
        static readonly public int msgIdSize = 2;

        private Socket m_socket;

        uint recvBuffSize;
        uint splitBuffSize;

        protected byte[] m_recvBuff;
        protected byte[] m_splitBuff;

        protected int m_curSplitPos;
        protected Queue<MsgObject> m_msgQueue;

        //Enigma encoder;
        //Enigma decoder;

        public bool isConnected
        {
            get { return m_state == ConnectionState.CONNECTED; }
        }

        public virtual void OnConnectSuccess() { }
        public virtual void OnDisconnected() { }

        public SocketConnection()
        {
        }

        public void Tick(float deltaTime)
        {
            if (m_state == ConnectionState.CONNECTING)
            {
                m_connectingTime += deltaTime;
                bool error = m_socket.Poll(0, SelectMode.SelectError);
                bool connected = m_socket.Poll(0, SelectMode.SelectWrite);
                
                if (connected && !error)
                {
                    m_state = ConnectionState.CONNECTED;
                    OnConnectSuccess();
                }
                else if (m_connectingTime > ConnectTimeout)
                {
                    m_state = ConnectionState.STOPPED;
                    Reset(true);
                }
            }
            else if(m_state == ConnectionState.CONNECTED)
            {
                if (!Receive())
                    return;
                //把发送队列清空
                SendQueue();
            }
        }

        public void Connect(string host, int f_port)
        {
            if (m_state != ConnectionState.STOPPED)
            {
                return;
            }
            
            try
            {
                IPAddress address = null;
                if (!IPAddress.TryParse(host, out address))
                {
                    IPHostEntry hostinfo = Dns.GetHostEntry(host);
                    IPAddress[] aryIP = hostinfo.AddressList;
                    address = aryIP[0];
                }
                IPEndPoint endpoint = new IPEndPoint(address, f_port);
                m_socket.Connect(endpoint);
            }
            catch(SocketException e)
            {
                if (e.SocketErrorCode == SocketError.IsConnected)
                {
                    m_state = ConnectionState.CONNECTED;
                    OnConnectSuccess();
                    return;
                }           
            }

            m_state = ConnectionState.CONNECTING;
        }

        public void Close()
        {
            if (m_state != ConnectionState.STOPPED)
            {
                try
                {
                    Debug.Log("Socket Shutdown");
                    m_socket.Shutdown(SocketShutdown.Both);
                }
                catch(SocketException e)
                {
                }
                m_socket.Close();
            }
            m_socket = null;
            m_state = ConnectionState.STOPPED;
        }


        public void Reset(bool isAbnormal)
        {
            recvBuffSize = 10240;
            splitBuffSize = 102400;

            Close(); 
            m_socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            m_socket.Blocking = false;
            m_socket.NoDelay = true;
            //encoder = new Enigma(0);
            //decoder = new Enigma(0);

            m_recvBuff = new byte[recvBuffSize];
            m_splitBuff = new byte[splitBuffSize];

            m_curSplitPos = 0;
            m_msgQueue = new Queue<MsgObject>();

            m_connectingTime = 0;

            m_sendQueue = new Queue<SendContext>();
            if (isAbnormal)
                OnDisconnected();
        }

        public void SendMessage(ushort f_id, MsgType type, byte[] f_buf)
        {
            int bufSize;
            if (f_buf == null)
            {
                bufSize = 0;
            }
            else
            {
                bufSize = f_buf.Length;
                //if (type == MsgType.JSON_REQUEST)
                //    f_buf = encoder.Encrypt(f_buf, 4);
                //else
                //    f_buf = encoder.Encrypt(f_buf, 0);
            }

            uint bodySize = (uint)(bufSize + msgIdSize + msgTypeSize + msgLengthSize);

            if (reverseByte)
            {
                bodySize = NetUtilites.ByteReverse_uint(bodySize);
                f_id = NetUtilites.ByteReverse_ushort(f_id);
            }

            byte[] data = new byte[bufSize + msgLengthSize + msgIdSize + msgTypeSize];

            BitConverter.GetBytes(bodySize).CopyTo(data, 0);
            BitConverter.GetBytes(f_id).CopyTo(data, msgLengthSize);
            
            data[msgLengthSize + msgIdSize] = (byte)type;

            if (f_buf != null)
            {
                f_buf.CopyTo(data, msgLengthSize + msgIdSize + msgTypeSize);
            }

            Send(data);
        }

        private void Send(byte[] f_data = null)
        {
            if (f_data != null)
            {
                SendContext context = new SendContext();
                context.Data = f_data;
                context.StartIndex = 0;
                m_sendQueue.Enqueue(context);
            }
        }

        void SendQueue()
        {
            try
            {
                while (m_state == ConnectionState.CONNECTED && m_sendQueue.Count > 0)
                {
                    SendContext currentContext = m_sendQueue.Peek();
                    int sendBytes = m_socket.Send(currentContext.Data, currentContext.StartIndex, currentContext.Data.Length - currentContext.StartIndex, SocketFlags.None);
                    currentContext.StartIndex += sendBytes;
                    if (currentContext.StartIndex >= currentContext.Data.Length)
                    {
                        m_sendQueue.Dequeue();
                    }
                }
            }
            catch (SocketException e)
            {
                if (e.SocketErrorCode != SocketError.WouldBlock)
                {
                    Debug.Log("Tcp Send Error: " + e.SocketErrorCode);
                    Reset(true);
                }
            }
        }

        bool Receive()
        {
            if (m_state == ConnectionState.CONNECTED)
            {
                try
                {
                    while (true)
                    {
                        int recvLen = m_socket.Receive(m_recvBuff);
                        if(recvLen == 0)
                        {
                            break;
                        }

                        ReadStream(recvLen);
                    }
                    
                }
                catch (SocketException ex)
                {
                    if (ex.SocketErrorCode != SocketError.WouldBlock)
                    {
                        Debug.LogWarning("Tcp Receive Error: " + ex.SocketErrorCode);
                        Reset(true);
                        return false;
                    }
                }
            }
            return true;
        }

        private void ReadStream(int recvLen)
        {
            bool isEnd = false;
            if (recvLen == 0)
                return;

            if (m_curSplitPos + recvLen > m_splitBuff.Length)
            {
                byte[] newBuff = new byte[m_curSplitPos + recvLen];
                Array.Copy(m_splitBuff, 0, newBuff,0,m_splitBuff.Length);
                m_splitBuff = newBuff;
            }

            Array.Copy(m_recvBuff, 0, m_splitBuff, m_curSplitPos, recvLen);
            m_curSplitPos += recvLen;

            while (!isEnd)
            {
                if (m_curSplitPos < msgLengthSize + msgIdSize + msgTypeSize)
                {
                    return;
                }

                byte[] thepacket = new byte[m_curSplitPos];
                Array.Copy(m_splitBuff, 0, thepacket, 0, m_curSplitPos);
                MemoryStream msgStream = new MemoryStream(thepacket, 0,m_curSplitPos);
                BinaryReader reader = new BinaryReader(msgStream,Encoding.Unicode);
                byte[] data = reader.ReadBytes(msgLengthSize);

                uint msgLen;
                if (reverseByte)
                {
                    msgLen = NetUtilites.ByteReverse_uint(data);
                }
                else
                {
                    msgLen = BitConverter.ToUInt32(data, 0);
                }

                if (m_curSplitPos >= msgLen)
                {
                    data = reader.ReadBytes(msgIdSize);
                    ushort msgID;
                    if (reverseByte)
                    {
                        msgID = NetUtilites.ByteReverse_ushort(data);
                    }
                    else
                    {
                        msgID = BitConverter.ToUInt16(data, 0);
                    }
                    data = reader.ReadBytes(msgTypeSize);
                    MsgType type = (MsgType) data[0];
                    data = reader.ReadBytes((int)(msgLen - msgIdSize - msgTypeSize- msgLengthSize));
                    //if (type == MsgType.JSON_RESPONSE)
                    //    data = decoder.Decrypt(data, 4);
                    //else
                    //    data = decoder.Decrypt(data, 0);
                    MsgObject msg = new MsgObject(msgID, data, type);
                    AddMsg(msg);
                    m_curSplitPos -= (int)msgLen;
                    NetUtilites.MemMove(m_splitBuff, (int)msgLen, 0, m_curSplitPos);
                }
                else
                {
                    isEnd = true;
                }
            }
        }

        public MsgObject GetMsg()
        {
            MsgObject retMsg = null;
            if (m_msgQueue.Count > 0)
            {
                retMsg = m_msgQueue.Dequeue();
            }
            return retMsg;
        }

        private void AddMsg(MsgObject f_msg)
        {
            m_msgQueue.Enqueue(f_msg);
        }

        public bool IsConnected()
        {
            return m_state == ConnectionState.CONNECTED;
        }
    }
}
